import java.nio.*;
import java.util.*;

public class Message
{
	public Ciphers cipher; //type of cipher
	public MessageType type; //type of message
	public byte[] r;//nonce sent by bob or alice
	public int sessionId;//session id sent by bob to alice
	public byte[] e;//bob's public key
	public byte[] n;//bob's public key
	public byte[] s;//random number produced by alice to create K 
	public byte[] hashOfMsgs;
	public byte[] data;
	public String errorMessage;
	
	public Message(MessageType type)
	{
		this.type = type;
	}
	
	public Message(byte[] msg) throws Exception
	{
		//get type
		int t = ByteToInt(msg, 0);	
		//range check
		if (t < 0 || t > 9)
			throw new Exception("Message type not recognized: " + t);
		
		type = MessageType.values()[t];
		
		if (type == MessageType.SessionStartRequest)
		{
			//get R
			r = Arrays.copyOfRange(msg, 4, 8);	
		}
		else if (type == MessageType.SessionStartResponce)
		{
			//get sessionId
			sessionId = ByteToInt(msg, 4);	
			
			//get public RSA key (certificate)
			e = Arrays.copyOfRange(msg, 8, 12);	
			n = Arrays.copyOfRange(msg, 12, 16);
			
			//get cipher
			int c = ByteToInt(msg, 16);
			cipher = Ciphers.values()[c];
			
			//get R
			r = Arrays.copyOfRange(msg, 20, 24);
		}
		else if (type == MessageType.SessionResumeRequest)
		{
			//get sessionId
			sessionId = ByteToInt(msg, 4);	
			
			//get R
			r = Arrays.copyOfRange(msg, 8, 12);	
		}
		else if (type == MessageType.SessionResumeResponce)
		{
			//get sessionId
			sessionId = ByteToInt(msg, 4);	
			
			//get cipher
			int c = ByteToInt(msg, 8);
			cipher = Ciphers.values()[c];
			
			//get R
			r = Arrays.copyOfRange(msg, 12, 16);		
			
			//get hash of msgs (128 bits / 16 bytes)
			hashOfMsgs = Arrays.copyOfRange(msg, 16, 32);
		}
		else if (type == MessageType.KeyedHashOfMsgs)
		{
			//get hash of msgs (128 bits / 16 bytes)
			hashOfMsgs = Arrays.copyOfRange(msg, 4, 20);
		}
		else if (type == MessageType.PreMasterSecret)
		{
			//get {S}bob
			s = Arrays.copyOfRange(msg, 4, 8);
		
			//get hash of msgs (128 bits / 16 bytes)
			hashOfMsgs = Arrays.copyOfRange(msg, 8, 24);
		}
		else if (type == MessageType.FileReceive || type == MessageType.FileSend)
		{
			//get data
			data = Arrays.copyOfRange(msg, 4, msg.length);
		}
		else if (type == MessageType.Error)
		{
			errorMessage = Utils.ByteToString(Arrays.copyOfRange(msg, 4, msg.length));
		}
	}
	
	public byte[] ToBytes()
	{
		if (type == MessageType.SessionStartRequest)
		{
			byte[] msg = new byte[8];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x00;
			
			//set R
			ByteCopy(msg, r, 4);
			
			return msg;
		}
		else if (type == MessageType.SessionStartResponce)
		{
			byte[] msg = new byte[24];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x01;
			
			//set sessionId
			byte[] bs = ByteBuffer.allocate(4).putInt(sessionId).array();
			ByteCopy(msg, bs, 4);
			
			//set public RSA key (certificate)
			ByteCopy(msg, e, 8);
			ByteCopy(msg, n, 12);			
			
			//set cipher
			byte[] bc = ByteBuffer.allocate(4).putInt(cipher.getValue()).array();
			ByteCopy(msg, bc, 16);
			
			//set R
			ByteCopy(msg, r, 20);
			
			return msg;
		}
		else if (type == MessageType.SessionResumeRequest)
		{
			byte[] msg = new byte[12];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x02;
			
			//set sessionId
			byte[] bs = ByteBuffer.allocate(4).putInt(sessionId).array();
			ByteCopy(msg, bs, 4);
			
			//set R	
			ByteCopy(msg, r, 8);
			
			return msg;
		}
		else if (type == MessageType.SessionResumeResponce)
		{
			byte[] msg = new byte[32];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x03;
			
			//set sessionId	
			byte[] bs = ByteBuffer.allocate(4).putInt(sessionId).array();
			ByteCopy(msg, bs, 4);
			
			//set cipher
			byte[] bc = ByteBuffer.allocate(4).putInt(cipher.getValue()).array();
			ByteCopy(msg, bc, 8);
			
			//set R	
			ByteCopy(msg, r, 12);
			
			//set hash of msgs (128 bits / 16 bytes)
			ByteCopy(msg, hashOfMsgs, 16);
			
			return msg;
		}
		else if (type == MessageType.KeyedHashOfMsgs)
		{
			byte[] msg = new byte[20];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x04;
			
			//set hash of msgs (128 bits / 16 bytes)
			ByteCopy(msg, hashOfMsgs, 4);
			
			return msg;
		}
		else if (type == MessageType.PreMasterSecret)
		{
			byte[] msg = new byte[24];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x05;
			
			//set {S}bob
			ByteCopy(msg, s, 4);
		
			//set hash of msgs (128 bits / 16 bytes)
			ByteCopy(msg, hashOfMsgs, 8);
			
			return msg;
		}
		else if (type == MessageType.FileReceive)
		{
			byte[] msg = new byte[4 + data.length];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x06;
			
			//set data
			ByteCopy(msg, data, 4);
			
			return msg;
		}
		else if (type == MessageType.FileSend)
		{
			byte[] msg = new byte[4 + data.length];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x07;
			
			//set data
			ByteCopy(msg, data, 4);
			
			return msg;
		}
		else if (type == MessageType.Error)
		{
			byte[] eb = errorMessage.getBytes();
			
			byte[] msg = new byte[4 + eb.length];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x08;
			
			//set data
			ByteCopy(msg, eb, 4);
			
			return msg;
		}
		else if (type == MessageType.SessionEstablished)
		{			
			byte[] msg = new byte[4];
			//set type
			msg[0] = 0x00;
			msg[1] = 0x00;
			msg[2] = 0x00;
			msg[3] = 0x09;
			
			return msg;
		}
		
		return null;
	}
		
	private int ByteToInt(byte[] msg, int from)
	{
		//get specified range
		byte[] bi = Arrays.copyOfRange(msg, from, from + 4);
		ByteBuffer bb = ByteBuffer.wrap(bi);
		//convert to int and return
		return bb.getInt();
	}
	
	private void ByteCopy(byte[] copyTo, byte[] copyFrom, int startAt)
	{
		int j = startAt;
		for (int i = 0; i < copyFrom.length; i++)
		{
			copyTo[j] = copyFrom[i];
			j++;
		}
	}
	
	public String toString()
	{
		String str = "";
		str += "Message Type: " + type.toString() + "\n";
		
		if (type == MessageType.SessionStartRequest)
		{
			str += "Ciphers: RC4, CFB, CBC, PCBC\n";
			str += "R: " + Utils.ToHexString(r) + "\n";
		}
		else if (type == MessageType.SessionStartResponce)
		{
			str += "Session ID: " + sessionId + "\n";
			str += "Centificate: " + Utils.ToHexString(e) + " mod " + Utils.ToHexString(n) + "\n";
			str += "Cipher: " + cipher.toString() + "\n";
			str += "R: " + Utils.ToHexString(r) + "\n";
		}
		else if (type == MessageType.SessionResumeRequest)
		{
			str += "Session ID: " + sessionId + "\n";
			str += "R: " + Utils.ToHexString(r) + "\n";
		}
		else if (type == MessageType.SessionResumeResponce)
		{
			str += "Session ID: " + sessionId + "\n";
			str += "Cipher: " + cipher.toString() + "\n";
			str += "R: " + Utils.ToHexString(r) + "\n";
			str += "hashOfMsgs: " + Utils.ToHexString(hashOfMsgs) + "\n";
		}
		else if (type == MessageType.KeyedHashOfMsgs)
			str += "hashOfMsgs: " + Utils.ToHexString(hashOfMsgs) + "\n";
		else if (type == MessageType.PreMasterSecret)
		{
			str += "S: " + Utils.ToHexString(s) + "\n";
			str += "hashOfMsgs: " + Utils.ToHexString(hashOfMsgs) + "\n";
		}
		else if (type == MessageType.FileReceive || type == MessageType.FileReceive)
			str += "Data Size: " + data.length + " bytes\n";
		else if (type == MessageType.Error)
			str += "Error msg: " + errorMessage;	
			
		return str;
	}
	
	public enum Ciphers
	{
		RC4(0),
		CFB(1),
		CBC(2),
		PCBC(3);
		
		private final int value;
		private Ciphers(int value) 
		{
			this.value = value;
		}

		public int getValue() 
		{
			return value;
		}
	}
	
	public enum MessageType
	{
		SessionStartRequest,
		SessionStartResponce,
		SessionResumeRequest,
		SessionResumeResponce,
		KeyedHashOfMsgs,
		PreMasterSecret,
		FileReceive,
		FileSend,
		Error,
		SessionEstablished
	}
}